﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Up2Link.Util
{
    /// <summary>
    /// 在命令控制符模式下实现非阻塞接受用户键盘输入并同时显示输出的类
    /// </summary>
    public class CConsole
    {
        /// <summary>
        /// 表示输出元素的类
        /// </summary>
        private class COutputElement
        {
            public ConsoleColor color;
            public String text;
            public Boolean newLine;

            public COutputElement(ConsoleColor c, String t, Boolean n)
            {
                color = c;
                text = t;
                newLine = n;
            }
        }

        /// <summary>
        /// 用于显示输出的线程
        /// </summary>
        private Thread output;

        /// <summary>
        /// 用于接受用户输入的线程
        /// </summary>
        private Thread input;

        /// <summary>
        /// 存储需要输出的信息的队列
        /// </summary>
        private Queue<COutputElement> outputStr = new Queue<COutputElement>();

        /// <summary>
        /// 存储接受到的用户输入的队列
        /// </summary>
        private Queue<String> inputStr = new Queue<String>();

        /// <summary>
        /// 运行状态标记
        /// </summary>
        private Boolean consoleOn = false;

        /// <summary>
        /// 命令控制符窗口的宽度
        /// </summary>
        private Int32 panelWidth = 80;

        /// <summary>
        /// 输入光标的水平位置
        /// </summary>
        private Int32 inputCursorLeft = 0;

        /// <summary>
        /// 输入光标的垂直位置
        /// </summary>
        private Int32 inputCursorTop = 22;

        /// <summary>
        /// 输入窗口高度
        /// 从输入光标的位置算起
        /// </summary>
        private Int32 inputPanelHeight = 3;

        /// <summary>
        /// 输出窗口的高度
        /// </summary>
        private Int32 outputPanelHeight = 19;

        /// <summary>
        /// 光标位置锁，在每次需要使用光标位置前锁定
        /// </summary>
        static private Object CursorPositionLock = new Object();


        /// <summary>
        /// 无参数构造函数
        /// 创建两个线程对象
        /// </summary>
        public CConsole()
        {
            output = new Thread(new ThreadStart(OutputUpdate));
            input = new Thread(new ThreadStart(InputUpdate));

        }

        /// <summary>
        /// 启动命令提示符非阻塞输入输出
        /// </summary>
        public void Start()
        {
            //初始化屏幕
            Console.Clear();

            //初始化光标位置
            Console.CursorLeft = 0;
            Console.CursorTop = outputPanelHeight + 1;

            //显示提示信息
            for (Int32 i = 0; i < panelWidth; i++)
            {
                Console.Write('=');
            }
            Console.WriteLine("请输入信息：");

            //修改运行状态标记
            consoleOn = true;

            //启动线程
            output.Start();
            input.Start();
        }

        /// <summary>
        /// 关闭命令提示符非阻塞输入输出
        /// </summary>
        public void Close()
        {
            //修改运行状态标记
            consoleOn = false;

            //等待线程结束
            output.Join();
            input.Join();
        }

        /// <summary>
        /// 将指定的字符串加入输出队列
        /// </summary>
        /// <param name="str">需要输出的字符串</param>
        public void SendOutput(String str)
        {
            //判断字符串是否为空
            if (str != null)
            {
                outputStr.Enqueue(new COutputElement(Console.ForegroundColor, str, true));
            }
        }

        public void SendOutput(String str, ConsoleColor color)
        {
            //判断字符串是否为空
            if (str != null)
            {
                outputStr.Enqueue(new COutputElement(color, str, false));
            }
        }

        public void SendOutput(String str, ConsoleColor color, Boolean newLine)
        {
            //判断字符串是否为空
            if (str != null)
            {
                outputStr.Enqueue(new COutputElement(color, str, newLine));
            }
        }

        /// <summary>
        /// 获得用户输入的字符串
        /// </summary>
        /// <returns>返回队列的第一个字符串，如果队列为空则返回null</returns>
        public String ReceiveInput()
        {
            //如果用户输入队列为空，则返回null
            if (inputStr.Count > 0)
            {
                return inputStr.Dequeue();
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 显示输入信息的线程
        /// </summary>
        private void OutputUpdate()
        {
            Int32 lineCount = 1;
            Int32 columnCount = 1;
            Boolean newLine = false;

            //循环条件为运行状态标记为真
            while (consoleOn)
            {
                //如果有输出信息需要显示
                if (outputStr.Count > 0)
                {
                    //获取需要输出的字符串
                    COutputElement element = outputStr.Dequeue();
                    String text = element.text;

                    //根据字符串长度计算字符串输出完成后的光标位置
                    //考虑到输出一个中文字符需要占用两个光标位置，这里取字符串长度的两倍作为输出字符串需要的最大宽度
                    Int32 endColumn = columnCount + text.Length * 2;
                    Int32 endLine = newLine ? lineCount + 1 : lineCount;

                    while (endColumn > panelWidth)
                    {
                        endColumn = endColumn - panelWidth;
                        endLine++;
                    }

                    lock (CursorPositionLock)
                    {
                        //如果字符串输出完成后的光标位置超出了显示区域，则将之前显示的内容全部上移，直到有足够的位置进行输出
                        if (endLine > outputPanelHeight)
                        {
                            Console.MoveBufferArea(0, endLine - outputPanelHeight, panelWidth, outputPanelHeight * 2 - endLine, 0, 0);
                            lineCount -= endLine - outputPanelHeight;
                        }

                        //修改光标位置
                        Int32 left = Console.CursorLeft;
                        Int32 top = Console.CursorTop;
                        Console.CursorLeft = newLine ? 0 : columnCount - 1;
                        Console.CursorTop = newLine ? lineCount : lineCount - 1;

                        //修改文字颜色
                        ConsoleColor color = Console.ForegroundColor;
                        Console.ForegroundColor = element.color;

                        //输出信息
                        Console.Write(text);
                        columnCount = Console.CursorLeft + 1;
                        lineCount = Console.CursorTop + 1;

                        //标记是否换行
                        newLine = element.newLine;

                        //重置文字颜色
                        Console.ForegroundColor = color;

                        //恢复光标位置
                        Console.CursorLeft = left;
                        Console.CursorTop = top;
                    }
                }

                Thread.Sleep(100);
            }
        }

        /// <summary>
        /// 接受用户输入的线程
        /// </summary>
        private void InputUpdate()
        {
            String str;

            //循环条件为运行状态标记为真
            while (consoleOn)
            {
                //等待用户输入，此线程会被阻塞
                str = Console.ReadLine();

                //将接受到的用户输入加入队列
                inputStr.Enqueue(str);

                lock (CursorPositionLock)
                {
                    //清除用户输入区域
                    Console.CursorLeft = 0;
                    Console.CursorTop = inputCursorTop;
                    for (Int32 i = 0; i < panelWidth * 2; i++)
                    {
                        Console.Write(' ');
                    }

                    Console.CursorLeft = inputCursorLeft;
                    Console.CursorTop = inputCursorTop;
                }
            }
        }
    }
}
